共计 7868 个字符,预计需要花费 20 分钟才能阅读完成。
提醒:本文最后更新于 2024-10-23 16:27,文中所关联的信息可能已发生改变,请知悉!
opencv 基础
该过程主要通过实际操作完成
素材
选取合适的高清图片,通过截屏生成新图片降低图片质量,将新的低质量图片命名为 text1.png
保存在 python 脚本的目录中
代码环境
python 解释器:anaconda3/python3.8
编译器:pycharm
编码:utf-8
代码
为了方便测试,只使用了一个脚本测试,学习笔记和部分运行结果也通过注释的方式简单加入
去除注释 #
即可运行
# python 解释器:anaconda3/python3.8
# 编译器:pycharm
# utf-8
# 一些测试过程以注释方式保留,以便以后查看
# 导入所需要的库,并给以简洁的名称
import numpy as np
import cv2 as cv
# 1
# 按指定方式读取图像
img = cv.imread('test1.png', 1) # 该步骤类似于 C 语言的文件指针
# 第一个参数为图片路径,不能含有中文等不兼容字符,否则报错,这里没办法只好使用了相对路径
# 第二个参数代表读取方式
# 1:加载彩色图像。任何图像的透明度都会被忽视。它是默认标志。# 0:以灰度模式加载图像
# -1:加载图像,包括 alpha 通道
# 2
# show 图像
# cv.imshow('test1', img) # 第一个参数是窗口名称。第二个参数是我们的对象。#
# cv.waitKey(0) # 以 0 为参数时,无限制等待用户按下任意键
# cv.destroyAllWindows() # 销毁窗口
# 3
# 访问和修改像素
# px = img[100, 100] # 该值与图像读入方式有关
# print(px)
# img[100, 100] = 255 # 灰度
# img[100, 100] = [255, 255, 255] # BGR
# print(img[100, 100])
# 查阅资料了解到上面的方法的效率并不是很高
# 可以使用 Numpy 数组方法 array.item()和 array.itemset()
# 经测试,因为某些未知原因,导致运行错误,这里先略去,以后再 debug
# 4
# 访问图像属性
# print(img.shape) # 访问图像形状
# 以灰度图像读入时,输出(674, 1200),仅返回行和列
# 以 BGR 读入时,输出(674, 1200, 3),多返回一位数,表示通道数
# print(img.size) # 访问图像像素总数
# 以灰度读入时,输出 808800
# 以 BGR 读入时,输出 2426400
# print(img.dtype) # 访问图像数据类型
# 输出 uint8
# img.dtype 在调试时非常重要,因为 OpenCV-Python 代码中的大量错误是由无效的数据类型引起的。# 5
# 拆分和合并图像通道
# B, G, R = cv.split(img) # 此时 img 由 BGR 方式读入,才可进行此操作
# print(B.shape) # 输出(674, 1200),与原图像数据相比仅缺少通道数,这表明拆分成功
# img = cv.merge((B, G, R)) # 进行这一步操作时,图像通过 BGR 方式读入,且上面的“B, G, R = cv.split(img)”需要先执行
# print(img.shape) # 输出(674, 1200, 3),与原图像数据完全相同,这表明合并成功
# 6
# 图像加法
# x = np.uint8([250])
# y = np.uint8([10])
# print(cv.add(x, y)) # 输出[[255]] 原理:opencv 的加法采用饱和运算 250+10=260->255
# print(x + y) # 输出[4] 原理:numpy 的加法采用模运算 (250+10)%256=4
# 两者相比较,使用时应该选用 opencv 的加法
opencv 进阶
注:这部分内容是有针对性的学习,暂时用不到的就没有学
性能衡量和提升技术
该部分内容,我只简单提取了 cv.useOptimized()
和cv.setUseOptimized()
两条命令
对于部分操作的运行速度,优化会比不优化快两倍,所以我觉得有必要注意
检查是否使用优化
import cv2 as cv # 导入 opencv 库,简化为 cv
cv.useOptimized() # 检查是否使用 opencv 优化,该函数值为 Ture 或者 False
print(cv.useOptimized()) # 打印该函数值,判断是否启用优化
启用 / 禁用优化
import cv2 as cv # 导入 opencv 库,简化为 cv
cv.setUseOptimized(Ture) # 启用优化
cv.setUseOptimized(False) # 禁用优化
其他性能优化技术
这部分内容暂时不学习,但是先做个摘记
有几种技术和编码方法可以充分利用 Python 和 Numpy 的最大性能。这里要注意的主要事情是,首先尝试以一种简单的方式实现算法。一旦它运行起来,分析它,找到瓶颈并优化它们。
1. 尽量避免在 Python 中使用循环,尤其是双 / 三重循环等。它们本来就很慢。
2. 由于 Numpy 和 OpenCV 已针对向量运算进行了优化,因此将算法 / 代码向量化到最大程度。
3. 利用缓存一致性。
4. 除非需要,否则切勿创建数组的副本。尝试改用视图。数组复制是一项昂贵的操作。
即使执行了所有这些操作后,如果你的代码仍然很慢,或者不可避免地需要使用大循环,请使用 Cython 等其他库来使其更快。
图像梯度
OpenCV 提供三种类型的梯度滤波器或高通滤波器,即 Sobel,Scharr 和 Laplacian
这是三个可以直接用的函数,暂时没搞清楚它们的原理,去了解了一下使用效果,进行实测后失败,原因未知,这里记录一下
官方使用的效果图如下:
可以看出 Laplacian 方法较为优秀
Canny 边缘检测
cv.Canny()
方法
这个算法,或许会对我的工作有借鉴意义,虽然思路上有很大不同,我把这种方法分类为矢量方法,而我认为我所需要做的工作属于标量方法。我个人的观点是:矢量方法更需要想象能力,标量方法更需要精密的思维;而有些东西是共通的,比如下面讲到的降噪、阈值思想
降噪
由于边缘检测容易受到图像中噪声的影响,因此第一步是使用 5x5 高斯滤波器消除图像中的噪声。
和上面的“图像梯度”一样,也需要降噪,由此可见,降噪在图像处理中是很重要的
查找图像的强度梯度
使用 Sobel 核在水平和垂直方向上对平滑的图像进行滤波,以在水平方向 (Gx) 和垂直方向 (Gy) 上获得一阶导数。渐变方向始终垂直于边缘。将其舍入为代表垂直,水平和两个对角线方向的四个角度之一。
非极大值抑制
在获得梯度大小和方向后,将对图像进行全面扫描,以去除可能不构成边缘的所有不需要的像素。为此,在每个像素处,检查像素是否是其在梯度方向上附近的局部最大值。
效果是能提取出细边
磁滞阈值
确定哪些边缘全部是真正的边缘,哪些不是。为此,我们需要两个阈值 minVal
和 maxVal
。强度梯度大于 maxVal
的任何边缘必定是边缘,而小于 minVal
的那些边缘必定是非边缘,因此将其丢弃。介于这两个阈值之间的对象根据其连通性被分类为边缘或非边缘。如果将它们连接到“边缘”像素,则将它们视为边缘的一部分。否则,它们也将被丢弃。
阈值说白了,就是人为搞个界限,挑选出较明显的边缘和非边缘,用于简化计算
另外,还有些特殊情况:边缘 A 在 maxVal
之上,因此被视为“确定边缘”。尽管边 C 低于 maxVal
,但它连接到边 A,因此也被视为有效边,我们得到了完整的曲线。但是边缘 B 尽管在 minVal
之上并且与边缘 C 处于同一区域,但是它没有连接到任何“确保边缘”,因此被丢弃。因此,非常重要的一点是我们必须相应地选择 minVal
和 maxVal
以获得正确的结果。
在边缘为长线的假设下,该阶段还消除了小像素噪声。因此,我们最终得到的是图像中的强边缘。
小总结
进阶部分就暂时学到这里(内容还有很多啊,但为了开始尝试一下自己的不一样的图像算法,还是先停下),不得不恭维一下 opencv 库,网上都说这是一个强大的库,但仅仅一个形容词“强大”,怎么能让我了解它,难不成仅仅是通过它的体积大,下载慢?
看过一些函数之后,才开始发自内心赞叹,比如单单拿出一个函数,让我封装起来,提供大部分语言的接口,供给通用场景使用,这就不是现在的我能做到的了,或许有一天我也可以吧。抽空得多看看这些函数的漂亮的源码。同时在接下来的任务中,我也期待着 opencv 能给我带来的新的震撼,或许会是仰止弥高,钻之弥坚。
numpy 的应用
在 OpenCV-Python Tutorials
中也常提到 numpy 库,即便在根本没用到它的代码示例中,也会来一行import numpy
在实践中也发现,numpy 能极大提高代码编写的效率;而查阅资料后发现,numpy 对数据的索引效率远高于不使用它的情况,所以 numpy 也是图像处理中的一大利器。
但是由于时间原因,暂时不像学习 opencv 一样对 numpy 进行系统的学习,这里就记录一些用法
结构体数组
import numpy as np
# 建立结构体类型
Mytype = np.dtype({'names': ['value', 'noise', 'part'], # value 像素值,noise 噪声值,part 分区(1/2)'formats': ['i', 'i', 'i'] # 这里都采用整型(numpy 对于变量的范围和类型要求严格)})
# 新建结构体数组,下面的代码能直接新建自定义类型的,初始化的数组
array = np.zeros((m, n), dtype=Mytype) #“(m, n)”定义数组的形式,这里为二维数组,m 行 n 列
numpy 数组排序
由于 numpy 建立的数组可以很复杂,所以 numpy 的排序函数的参数也很多
import numpy
numpy.sort(a, axis=-1, kind=None, order=None)
# a : 要排序的数组
# axis:按什么轴进行排序,默认按最后一个轴进行排序
# kind:排序方法,默认是快速排序
# order : 当数组定义了字段属性时,可以按照某个属性进行排序
图像处理实战
测试 1
思路
将素材图片(同附件)读入,然后通过两种划分方法的遍历比较,得出噪声值(次数),修改像素操作(容易实现)及其他特殊情况的优化先不考虑
代码
(同附件)
# python 解释器:anaconda3/python3.8
# 编译器:pycharm
# utf-8
import numpy as np
import cv2 as cv
import heapq
# 创建管理像素噪声值及分区的结构体
Pixel_type = np.dtype({'names': ['value', 'noise', 'part'], # value 像素值,noise 噪声值,part 分区(1/2)'formats': ['i', 'i', 'i']
})
# 判断区分度
def judge_division_degree(array):
p1 = []
p2 = []
for i in range(0, len(array)):
if array[i]['part'] == 1:
p1.append(array[i]['value'])
elif array[i]['part'] == 0:
p2.append(array[i]['value'])
# 防止列表为空
if len(p1) == 0 or len(p2) == 0:
return False
expected_degree = 0
if min(p2) - max(p1) <= expected_degree:
return False
else:
return True
# 划分方法一(不考虑位置)def division_method1(array):
# 使 p 数组为有序集(p 数组为一维数组,无需考虑轴)
p = np.sort(array, order='value')
# p1 为 p 中相邻两数之差的数组
p1 = []
for i in range(0, len(p) - 1):
p1.append(p[i + 1]['value'] - p[i]['value'])
# 找出最大差值的下标
max_index = p1.index(max(p1))
# 建立较小数下标不重复数组
small_indexs = list(set(heapq.nsmallest(max_index + 1, p['value'])))
# 将较小数划分到区域一,标记为“1”,反之,标记仍为“0”的在区域二
for m in range(0, len(small_indexs)):
for n in range(0, len(array)):
if small_indexs[m] == array[n]['value']:
array[n]['part'] = 1
# 返回被标记好的数组
return array
# 划分方法二(考虑位置)def division_method2(array):
# 建立数组 p,存放顺时针方向像素的差值(前 - 后)
p = []
for i in range(0, len(array) - 1):
p.append(array[i]['value'] - array[i + 1]['value'])
p.append(array[-1]['value'] - array[0]['value'])
min_index = p.index(min(p))
max_index = p.index(max(p))
# 始终将较小部分的数标记为 1
if min_index > max_index:
for i in range(max_index, min_index + 1):
array[i]['part'] = 1
elif min_index < max_index:
for i in range(0, len(p)):
array[i]['part'] = 1
for i in range(min_index, max_index + 1):
array[i]['part'] = 0
# 返回被标记好的数组
return array
# 检查噪声点,传入的参数为单色图像通道
def noise_check(image_channel):
# 锁定图像边界
(x, y) = image_channel.shape
# 建立二维通道数组
b = np.zeros((x, y), dtype=Pixel_type)
# 先建立数组
for i in range(0, x):
for j in range(0, y):
# 存入像素值,并初始化噪声值和分区
b[i][j] = (image_channel[i][j], 0, 0)
# 因为接下来的操作需要提取一个个完整的九宫格
# 所以遍历像素点时,图像最边缘的像素点永不成为中心像素点
# 即:从 1 开始到最大值 - 1 结束
for i in range(1, x - 1):
for j in range(1, y - 1):
# 建立八邻域的数组,围绕中心像素点,按顺时针标记八个像素点
eight_neighbor1 = np.zeros(8, dtype=Pixel_type)
eight_neighbor1[0] = b[i - 1][j - 1]
eight_neighbor1[1] = b[i - 1][j]
eight_neighbor1[2] = b[i - 1][j + 1]
eight_neighbor1[3] = b[i][j + 1]
eight_neighbor1[4] = b[i + 1][j + 1]
eight_neighbor1[5] = b[i + 1][j]
eight_neighbor1[6] = b[i + 1][j - 1]
eight_neighbor1[7] = b[i][j - 1]
# 划分方法一
eight_neighbor_division1 = division_method1(eight_neighbor1)
# 建立八邻域的数组,围绕中心像素点,按顺时针标记八个像素点
eight_neighbor2 = np.zeros(8, dtype=Pixel_type)
eight_neighbor2[0] = b[i - 1][j - 1]
eight_neighbor2[1] = b[i - 1][j]
eight_neighbor2[2] = b[i - 1][j + 1]
eight_neighbor2[3] = b[i][j + 1]
eight_neighbor2[4] = b[i + 1][j + 1]
eight_neighbor2[5] = b[i + 1][j]
eight_neighbor2[6] = b[i + 1][j - 1]
eight_neighbor2[7] = b[i][j - 1]
# 划分方法二
eight_neighbor_division2 = division_method2(eight_neighbor2)
# 判断区分度
if judge_division_degree(eight_neighbor_division1) and \
judge_division_degree(eight_neighbor_division2):
# 找出划分区域不一样的像素点
for k in range(0, 8):
if eight_neighbor_division1[k]['part'] != eight_neighbor_division2[k]['part']:
if k == 0:
b[i - 1][j - 1]['noise'] += 1
elif k == 1:
b[i - 1][j]['noise'] += 1
elif k == 2:
b[i - 1][j + 1]['noise'] += 1
elif k == 3:
b[i][j + 1]['noise'] += 1
elif k == 4:
b[i + 1][j + 1]['noise'] += 1
elif k == 5:
b[i + 1][j]['noise'] += 1
elif k == 6:
b[i + 1][j - 1]['noise'] += 1
elif k == 7:
b[i][j - 1]['noise'] += 1
return b
# 以 BGR 方式读入图片
img = cv.imread('test3.png', 1)
# 检查读入是否成功
# print(img.shape)
# 输出(302, 302, 3),代表成功
# 拆分图像通道
B, G, R = cv.split(img)
# 调用 noise_check() 函数,检查可能的噪声点
b_px = noise_check(B) # 以 B 通道举例
# 在 output。txt 文件下输出噪声值的矩阵排布
pf = open('output.txt', 'w')
for i1 in range(0, 302):
for j1 in range(0, 302):
print(b_px[i1][j1]['noise'], end='', file=pf)
print('', file=pf)
print(" 导出成功 ")